package com.ycxy.school.express.service.impl;

import com.alibaba.fastjson.JSON;
import com.ycxy.school.express.ex.ServiceException;
import com.ycxy.school.express.mapper.UserMapper;
import com.ycxy.school.express.mapper.UserRoleMapper;
import com.ycxy.school.express.pojo.dto.UserAddNewDTO;
import com.ycxy.school.express.pojo.dto.UserLoginDTO;
import com.ycxy.school.express.pojo.dto.UserUpdateDTO;
import com.ycxy.school.express.pojo.entity.Address;
import com.ycxy.school.express.pojo.entity.User;
import com.ycxy.school.express.pojo.entity.UserRole;
import com.ycxy.school.express.pojo.vo.UserListItemVO;
import com.ycxy.school.express.pojo.vo.UserStandardVO;
import com.ycxy.school.express.security.LoginPrincipal;
import com.ycxy.school.express.security.UserDetails;
import com.ycxy.school.express.service.IUserService;
import com.ycxy.school.express.web.ServiceCode;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;

/**
 * @Author jayLuo
 * @Date 2023/1/4 15:15
 * @PackageName:com.ycxy.school.express.service.impl
 * @ClassName: UserServiceImpl
 * @Description: TODO
 * @Version 1.0
 */
@Slf4j
@Service
public class UserServiceImpl implements IUserService {
    @Value("${school-express.jwt.secret-key}")
    private String secretKey;
    @Value("${school-express.jwt.duration-in-minute}")
    private long durationInMinute;
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private AuthenticationManager authenticationManager;

    public UserServiceImpl() {
        log.info("创建业务对象：userServiceImpl");
    }

    @Override
    public String login(UserLoginDTO userLoginDTO) {
        log.debug("开始处理【用户登录】的业务，参数：{}", userLoginDTO);
        // 执行认证
        Authentication authentication
                = new UsernamePasswordAuthenticationToken(
                userLoginDTO.getUsername(), userLoginDTO.getPassword());
        Authentication authenticateResult
                = authenticationManager.authenticate(authentication);
        log.debug("认证通过，认证管理器返回：{}", authenticateResult);

        // 从认证结果中获取所需的数据，将用于生成JWT
        Object principal = authenticateResult.getPrincipal();
        log.debug("认证结果中的当事人类型：{}", principal.getClass().getName());
        UserDetails userDetails = (UserDetails) principal;
        String username = userDetails.getUsername();
        Long id = userDetails.getId();

        // 生成JWT数据时，需要填充装到JWT中的数据
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", id);
        claims.put("username", username);
        log.debug("向JWT中存入id：{}", id);
        log.debug("向JWT中存入username：{}", username);
        // 以下是生成JWT的固定代码
        Date date = new Date(System.currentTimeMillis() + durationInMinute * 60 * 1000L);
        String jwt = Jwts.builder()
                // Header
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("typ", "JWT")
                // Payload
                .setClaims(claims)
                // Signature
                .setExpiration(date)
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
        log.debug("生成的JWT：{}", jwt);
        return jwt;
    }

    @Transactional
    @Override
    public void addNew(UserAddNewDTO userAddNewDTO) {
        log.debug("开始处理【添加用户】的业务，参数：{}", userAddNewDTO);

        log.debug("即将检查用户名是否被占用……");
        {
            // 从参数对象中获取username
            String username = userAddNewDTO.getUsername();
            // 调用userMapper的countByUsername()方法执行统计查询
            int count = userMapper.countByUsername(username);
            // 判断统计结果是否不等于0
            if (count != 0) {
                // 是：抛出异常
                String message = "添加用户失败，用户名【" + username + "】已经被占用！";
                log.debug(message);
                throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
            }
        }

        log.debug("即将检查手机号码是否被占用……");
        {
            // 从参数对象中获取手机号码
            String phone = userAddNewDTO.getPhone();
            // 调用userMapper的countByPhone()方法执行统计查询
            int count = userMapper.countByPhone(phone);
            // 判断统计结果是否不等于0
            if (count != 0) {
                // 是：抛出异常
                String message = "添加用户失败，手机号码【" + phone + "】已经被占用！";
                log.debug(message);
                throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
            }
        }

        // 创建user对象
        User user = new User();
        LocalDateTime now = LocalDateTime.now();
        // 通过BeanUtils.copyProperties()方法将参数对象的各属性值复制到user对象中
        BeanUtils.copyProperties(userAddNewDTO, user);
        // 取出密码，进行加密处理，并将密文封装回user对象中
        String rawPassword = user.getPassword();
        String encodedPassword = passwordEncoder.encode(rawPassword);
        user.setPassword(encodedPassword);
        user.setEnable(1);
        user.setCreateData(now);
        // 调用userMapper的insert()方法插入数据
        log.debug("即将插入用户数据，参数：{}", user);
        int rows = userMapper.insert(user);
        // 判断插入数据的结果是否符合预期
        if (rows != 1) {
            String message = "添加用户失败，服务器忙，请稍后再次尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_INSERT, message);
        }

        // 插入用户与角色关联的数据
        Long[] roleIds = userAddNewDTO.getRoleIds();
        UserRole[] userRoles = new UserRole[roleIds.length];
        for (int i = 0; i < roleIds.length; i++) {
            UserRole userRole = new UserRole();
            userRole.setUserId(user.getId());
            userRole.setRoleId(roleIds[i]);
            userRole.setCreateData(now);
            userRoles[i] = userRole;
        }
        rows = userRoleMapper.insertBatch(userRoles);
        if (rows != roleIds.length) {
            String message = "添加用户失败，服务器忙，请稍后再次尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_INSERT, message);
        }
    }

    @Transactional
    @Override
    public void delete(Long id) {
        log.debug("开始处理【删除用户】的业务，参数：{}", id);
        // id值为1的用户不允许被删除
        if (id == 1) {
            // 是：抛出异常（ERR_NOT_FOUND）
            String message = "删除用户失败，尝试删除的用户数据不存在！";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }

        // 调用userMapper的userStandardVO getStandardById(Long id)方法执行查询
        UserStandardVO queryResult = userMapper.getStandardById(id);
        // 判断查询结果是否为null
        if (queryResult == null) {
            // 是：抛出异常（ERR_NOT_FOUND）
            String message = "删除用户失败，尝试删除的用户数据不存在！";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }

        // 调用userMapper的int deleteById(Long id)执行删除，并获取返回的行数
        int rows = userMapper.deleteById(id);
        // 判断返回的行数是否不为1
        if (rows != 1) {
            // 是：抛出异常（ERR_DELETE：服务器忙）
            String message = "删除用户失败，服务器忙，请稍后再尝试！";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_DELETE, message);
        }

        // 删除用户与角色的关联数据
        rows = userRoleMapper.deleteByUserId(id);
        if (rows < 1) {
            // 是：抛出异常（ERR_DELETE：服务器忙）
            String message = "删除用户与角色管理数据失败，服务器忙，请稍后再尝试！";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_DELETE, message);
        }
    }

    @Override
    public void updateInfoById(Long id, UserUpdateDTO userUpdateDTO) {
        log.debug("开始处理【更新用户详情】的业务，参数ID：{},新数据: {}", id, userUpdateDTO);
        // 调用Mapper对象的getDetailsById()方法执行查询
        UserStandardVO queryResult = userMapper.getStandardById(id);
        // 判断查询结果是否为null
        if (queryResult == null) {
            // 是：此id对应的数据不存在，则抛出异常(ERR_NOT_FOUND)
            String message = "修改用户详情失败，尝试访问的数据不存在！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }

        User user = new User();
        LocalDateTime now = LocalDateTime.now();
        BeanUtils.copyProperties(userUpdateDTO,user);
        // 取出密码，进行加密处理，并将密文封装回user对象中
        String rawPassword = user.getPassword();
        String encodedPassword = passwordEncoder.encode(rawPassword);
        user.setId(id);
        user.setPassword(encodedPassword);
        user.setModifiedData(now);
        //修改数据
        log.debug("即将修改数据：{}", user);
        int rows = userMapper.update(user);
        if (rows != 1) {
            String message = "修改用户详情失败，服务器忙，请稍后再次尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_UPDATE, message);
        }
    }

    @Override
    public void updateLoginUserDetails(UserUpdateDTO userUpdateDTO) {
        log.debug("开始处理【更新用户详情】的业务,新数据: {}",userUpdateDTO);
        // 调用Mapper对象的getDetailsById()方法执行查询
        UserStandardVO queryResult = userMapper.getStandardById(getUserId());
        // 判断查询结果是否为null
        if (queryResult == null) {
            // 是：此id对应的数据不存在，则抛出异常(ERR_NOT_FOUND)
            String message = "修改用户详情失败，尝试访问的数据不存在！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }

        User user = new User();
        LocalDateTime now = LocalDateTime.now();
        BeanUtils.copyProperties(userUpdateDTO,user);
        // 取出密码，进行加密处理，并将密文封装回user对象中
        String rawPassword = user.getPassword();
        String encodedPassword="";
        if(rawPassword != null){
            encodedPassword = passwordEncoder.encode(rawPassword);
            user.setPassword(encodedPassword);
        }
        user.setId(getUserId());
        user.setModifiedData(now);
        //修改数据
        log.debug("即将修改数据：{}", user);
        int rows = userMapper.update(user);
        if (rows != 1) {
            String message = "修改用户详情失败，服务器忙，请稍后再次尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_UPDATE, message);
        }
    }

    @Override
    public void setEnable(Long id) {
        updateEnableById(id, 1);
    }

    @Override
    public void setDisable(Long id) {
        updateEnableById(id, 0);
    }

    @Override
    public List<UserListItemVO> list() {
        log.debug("开始处理【查询用户列表】的业务，无参数");
        List<UserListItemVO> list = userMapper.list();
        Iterator<UserListItemVO> iterator = list.iterator();
        while (iterator.hasNext()) {
            UserListItemVO item = iterator.next();
            if (item.getId() == 1) {
                iterator.remove();
                break;
            }
        }
        return list;
    }

    @Override
    public int count() {
        log.debug("开始处理【统计所有用户数量】的业务");
        int rows = userMapper.count();
        return rows;
    }

    @Override
    public int countByEnable() {
        log.debug("开始处理【统计已开启的用户数量】的业务");
        int rows = userMapper.countByEnable(1);
        return rows;
    }

    @Override
    public int countByDisable() {
        log.debug("开始处理【统计已禁用的用户数量】的业务");
        int rows = userMapper.countByEnable(0);
        return rows;
    }

    @Override
    public UserStandardVO getUserDetails(Long id) {
        log.debug("开始处理【根据id查询用户详情】的业务");
        UserStandardVO userDetails = userMapper.getStandardById(id);
        return userDetails;
    }

    @Override
    public UserStandardVO getLoginUserDetails() {
        log.debug("开始处理【查询当前登录用户详情】的业务");
        UserStandardVO userDetails = userMapper.getStandardById(getUserId());
        return userDetails;
    }

    private void updateEnableById(Long id, Integer enable) {
        String[] tips = {"禁用", "启用"};
        log.debug("开始处理【{}用户】的业务，参数：{}", tips[enable], id);

        // 判断参数id是否为1
        if (id == 1) {
            String message = tips[enable] + "用户失败，尝试访问的数据不存在！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }

        // 检查尝试访问的数据是否存在
        UserStandardVO queryResult = userMapper.getStandardById(id);
        if (queryResult == null) {
            String message = tips[enable] + "用户失败，尝试访问的数据不存在！";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }

        // 判断状态是否冲突（当前已经是目标状态）
        if (queryResult.getEnable().equals(enable)) {
            String message = tips[enable] + "用户失败，用户账号已经处于" + tips[enable] + "状态！";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }

        // 准备执行更新
        User user = new User();
        LocalDateTime now = LocalDateTime.now();
        user.setId(id);
        user.setEnable(enable);
        user.setModifiedData(now);
        int rows = userMapper.update(user);
        if (rows != 1) {
            String message = tips[enable] + "用户失败，服务器忙，请稍后再次尝试！";
            log.warn(message);
            throw new ServiceException(ServiceCode.ERR_UPDATE, message);
        }
    }
    public LoginPrincipal getUserInfo(){
        // 编写获取SpringSecurity上下文的代码
        LoginPrincipal principal = (LoginPrincipal)
                SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        // 为了逻辑严谨,判断一下SpringSecurity上下文中信息是不是null
        if(principal == null){
            throw new ServiceException(
                    ServiceCode.ERR_UNAUTHORIZED,"您没有登录!");
        }
        return principal;
    }

    // 业务逻辑层需求中,实际上只需要用户的id
    // 我们可以再编写一个方法,从用户对象中获取id
    public Long getUserId(){
        return getUserInfo().getId();
    }
}
